Skip to content
Built 26/04/17 09:39commit 8de3d61

中文 | English

作者为 Anthropic Labs 团队成员 Prithvi Rajasekaran。

在过去几个月里,我一直在解决两个相互关联的问题:如何让 Claude 产出高质量的前端设计,以及如何让它在没有人工干预的情况下构建完整应用。这项工作起源于我们更早的 frontend design skilllong-running coding agent harness 尝试。当时我和同事们通过 prompt engineering 与 harness 设计,把 Claude 的表现提升到了远超基线的水平,但两条路径最后都撞上了天花板。

为了突破这些限制,我开始寻找能跨越两个完全不同领域的 AI 工程方法:一类是由主观审美定义的设计问题,另一类是能用可验证正确性和可用性来衡量的编码问题。受到 Generative Adversarial Networks(GAN)启发,我设计了一种由 generatorevaluator 组成的多 agent 结构。要让 evaluator 能稳定地、而且带着品味地给输出打分,第一步就是先建立一套标准,把“这个设计好不好”这类主观判断转成可落地评分的具体维度。

随后,我把这些方法应用到了长时间运行的自主编码任务中,并延续了我们早期 harness 工作里的两个经验:把构建过程拆成可处理的块,以及用结构化产物在不同 session 之间传递上下文。最终形成的是一个 planner、generator、evaluator 三 agent 架构,它能在多小时的自主编码 session 中产出完整且丰富的全栈应用。

为什么朴素实现会失效

我们之前已经展示过,harness 设计会显著影响长时间运行 agent 编码任务的效果。在更早的 实验 中,我们使用一个 initializer agent 把产品规格拆成任务列表,再让 coding agent 一次实现一个功能,并通过交接产物把上下文跨 session 延续下去。更广泛的开发者社区也逐渐收敛到了类似结论,例如 Ralph Wiggum 这类方法会通过 hooks 或脚本让 agent 持续处于迭代循环中。

但还有一些问题始终存在。任务一复杂,agent 往往会随着运行时间变长而逐渐失控。我们拆解这个问题时,观察到了两种常见失效模式。

第一种是,随着上下文窗口被填满,模型在长任务中的一致性会逐步下降(参见我们关于 context engineering 的文章)。有些模型还会表现出“context anxiety”,也就是当它们感觉自己快到上下文极限时,会过早开始收尾。Context reset 能同时解决这两个问题:彻底清空上下文,启动一个全新的 agent,再通过结构化交接产物把前一个 agent 的状态和下一步计划传给新 agent。

这和 compaction 不同。compaction 是把对话前面的部分在原地压缩成总结,让同一个 agent 用更短的历史继续工作。虽然 compaction 能保留连续性,但并没有给 agent 一个真正的“干净状态”,因此 context anxiety 仍可能持续。reset 的优点是给了 agent 干净的上下文,代价是交接产物必须携带足够多的状态,好让下一位 agent 能平滑接手。我们早期测试中发现,Claude Sonnet 4.5 的 context anxiety 强到只靠 compaction 还不够,因此 context reset 变成了 harness 设计的核心部分。它解决了根问题,但也带来了编排复杂度、额外 token 成本和更长延迟。

第二个之前还没充分处理的问题,是 自我评估。当 agent 被要求给自己产出的工作打分时,它通常会非常自信地夸奖自己,即使在旁观的人看来,质量其实明显平庸。这个问题在设计这类主观任务里尤为严重,因为这里没有类似软件测试那样的二元验证标准。一个布局“是不是精致”本来就是主观判断,而 agent 在给自己打分时几乎总是偏正面。

不过,即便是在那些可以客观验证结果的任务中,agent 也仍然常常会表现出不够可靠的判断力,从而拖慢任务完成。把“做事”的 agent 和“评判”的 agent 分开,是解决这个问题的一个强杠杆。光靠分离本身并不能立刻消除宽松倾向;evaluator 仍然是 LLM,也天生倾向于对 LLM 产出更宽容。但把一个独立 evaluator 调成“怀疑模式”,比让 generator 对自己严苛得多要容易得多,而且一旦这种外部反馈存在,generator 就有了具体且可迭代的改进目标。

前端设计:让主观质量变得可评分

我最开始在前端设计上做实验,因为自我评估问题在这里最显眼。如果不加任何干预,Claude 往往会收敛到安全、可预测但视觉上没什么亮点的布局上。

有两个洞见塑造了我为前端设计构建的 harness。第一,虽然审美不可能被完全压缩成一个分数,而且个人口味永远会不同,但只要把设计原则和偏好编码进评分标准,设计质量就是可以被改善的。“这个设计美吗?” 很难稳定回答,但 “它是否符合我们定义的好设计原则?” 就给了 Claude 一个更具体的评判对象。第二,把前端生成与前端评审分开,就能建立一个反馈回路,持续把 generator 推向更强的输出。

基于这点,我为 generator 和 evaluator 写了同样的四项评分标准:

  • Design quality:设计是否像一个整体,而不是零散部件的拼接?优秀设计意味着色彩、字体、布局、图像和细节能共同形成明确的气质和身份感。
  • Originality:有没有看得出是“自定义选择”的痕迹,还是只是模板布局、库默认样式和 AI 常见模式?一个人类设计师应当能看出其中存在刻意的创造性选择。未改动的 stock 组件,或者一眼能认出的 AI 套路(例如紫色渐变配白卡片)在这里都应该失败。
  • Craft:技术执行质量,例如字体层级、留白一致性、色彩和谐度、对比度等。这是能力检查而非创造力检查。大多数合理实现默认都做得还不错;失败意味着基础功不到位。
  • Functionality:独立于审美的可用性。用户能否理解界面做什么、找到主要操作,并且无需猜测就能完成任务。

我刻意把 design quality 和 originality 的权重放在 craft 和 functionality 之上。Claude 在 craft 和 functionality 上本来就表现不错,因为这些技术能力通常会自然出现。但在 design 和 originality 上,Claude 经常只能产出平庸甚至乏味的东西。标准中我明确惩罚了高度通用的 “AI slop” 模式,并通过提高设计与原创性的权重,把模型逼向更大胆的审美风险。

我还用 few-shot 示例校准 evaluator,为它提供带详细打分拆解的例子。这保证了 evaluator 的判断更接近我的偏好,并减少了迭代过程中的评分漂移。

整个循环建立在 Claude Agent SDK 上,因此编排本身比较直接。generator agent 根据用户 prompt 先生成 HTML/CSS/JS 前端;我给 evaluator 配了 Playwright MCP,让它在打分前能直接操作真实页面,并针对每一项标准写出详细批评。实际运行中,evaluator 会自行浏览页面、截图、细看实现,然后给出评估。这个反馈再回流给 generator,作为下一轮迭代输入。每次生成我一般会跑 5 到 15 轮,而随着 generator 根据 evaluator 的批评不断调整,结果通常会变得越来越有辨识度。因为 evaluator 是在真实页面上主动导航,而不是只看静态截图,所以每轮都需要真实的 wall-clock 时间,整次运行最长可达 4 小时。我还要求 generator 在每轮评估后做策略性决策:如果分数走势不错,就沿着当前方向继续细化;如果方向不对,就直接转向一种完全不同的审美路线。

从多次运行看,evaluator 的评分通常会随着迭代上升后趋于平台期,而且仍然保留一些上行空间。有些生成是渐进式变好,有些则会在迭代之间出现剧烈的审美跳转。

评分标准的措辞也会在我没有完全预料到的地方影响 generator。比如加入 “the best designs are museum quality” 这种措辞,会把设计推向某种特定的视觉收敛方向,说明与评分标准配套的 prompt 本身也在塑造输出的气质。

虽然总体上分数会随着迭代提升,但模式并不总是线性。有些中间版本我其实更喜欢,甚至胜过最后一个版本。随着轮数增加,实现复杂度往往也会上升,因为 generator 会根据 evaluator 的反馈尝试更雄心勃勃的方案。即便在第一轮,输出也已经明显好于完全没有额外提示的基线版本,这说明这些评分标准以及其措辞,本身就足以在 evaluator 反馈尚未开始前,把模型从通用默认套路中拽出来。

一个很典型的例子是我让模型做一个荷兰艺术博物馆的网站。到了第九轮,它已经产出一个干净、深色主题的虚构博物馆落地页,视觉上很成熟,但整体还在我的预期范围内。到了第十轮,它却彻底推翻原方向,把网站重构成一种“空间体验”:一个用 CSS perspective 渲染出的 3D 房间、棋盘格地板、自由摆放在墙上的画作,以及通过门洞在不同展厅之间导航,而不再依赖滚动或点击。这种级别的创造性跃迁,是我在单轮生成里从未见过的。

扩展到全栈编码

有了这些结论之后,我把这种受 GAN 启发的模式应用到了全栈开发中。generator-evaluator 循环与软件开发生命周期天然契合,代码评审和 QA 在结构上就扮演了与设计 evaluator 相同的角色。

架构

在之前的 long-running harness 中,我们已经解决了如何让多 session 编码保持一致性:使用 initializer agent、一次处理一个功能的 coding agent,以及 session 之间的 context reset。Context reset 是关键突破:当时的 harness 基于 Sonnet 4.5,而它明显带有前面说到的 “context anxiety”。为了让模型保持在任务上,我们必须设计一个能在反复 reset 的情况下仍能正常运作的 harness。到了 Opus 4.5,这种倾向几乎被模型本身消除了,因此我在新 harness 中完全去掉了 context reset,改成让 agent 在整个 build 过程中作为一个连续 session 运行,并用 Claude Agent SDK 的自动 compaction 来处理上下文增长。

基于最初版本的经验,我构建了一个三 agent 系统,每个 agent 都对应我之前观察到的一个具体缺口:

Planner: 以前的 harness 要求用户一开始就提供非常详细的规格说明。我想把这一步也自动化,于是做了一个 planner agent,它可以把一段 1 到 4 句的简短需求扩展成完整的产品规格。我要求它在 scope 上更有野心,并把重点放在产品上下文和高层技术设计上,而不是过早写死实现细节。原因是,如果 planner 一开始就写进了错误的底层技术细节,这些错误会一路传导到下游实现中。更好的策略是只约束 agent 必须交付什么,再让它们在执行时自己找路径。我还要求 planner 主动寻找把 AI 功能嵌进产品规格里的机会。

Generator: “一次一个功能” 的方式在早期 harness 里对 scope 管理很有效。我继续采用这一思路,让 generator 以 sprint 为单位推进工作,每个 sprint 从规格里挑出一个功能块去做。每个 sprint 都用 React、Vite、FastAPI 和 SQLite(后来换成 PostgreSQL)来实现应用,并要求 generator 在每个 sprint 末尾先自评,再交给 QA。它也拥有 git 作为版本控制工具。

Evaluator: 早期 harness 里生成的应用,看上去经常很唬人,但一旦真正操作就能发现 bug。为此,evaluator 通过 Playwright MCP 像真实用户一样点击运行中的应用,测试 UI 功能、API endpoint 和数据库状态。然后它会用一套从前端实验演化来的标准给每个 sprint 打分,覆盖产品深度、功能性、视觉设计和代码质量。每个维度都有硬阈值,只要有一项没过线,整个 sprint 就判失败,generator 会收到详细的修复反馈。

在每个 sprint 开始之前,generator 和 evaluator 还要先协商一个 sprint contract:在写代码之前就约定本轮“done”到底意味着什么。之所以要加这一步,是因为产品规格被我刻意保持在高层,我需要一个桥接步骤,把用户故事转换成可测试的实现目标。generator 先提出自己这轮要做什么、怎么验证完成;evaluator 再审核这份提案,确保 generator 做的是正确的东西。两者会来回迭代,直到都达成一致。

这些 agent 通过文件通信:一个 agent 写文件,另一个 agent 读文件,再直接在文件里回复或另写新文件让前一个 agent 来读。这样 generator 就是在双方已达成一致的契约上做实现,再交给 QA。它既能忠实于规格,又避免了过早把实现细节写死。

运行这个 harness

这个 harness 的第一个版本使用的是 Claude Opus 4.5。我会把同一个用户 prompt 同时喂给完整 harness 和一个单 agent 系统做对照,因为当时 Opus 4.5 是我们最强的编码模型。

我当时写的 prompt 是:

Create a 2D retro game maker with features including a level editor, sprite editor, entity behaviors, and a playable test mode.

完整 harness 的运行时间和成本比单 agent 高出很多,但输出质量差距也一眼就能看出来。

一开始,我对单 agent 版本的期待是:我能构建一个关卡及其组成部分(精灵、实体、tile 布局),然后点一下 play 真正玩这个关卡。刚打开应用时,它看上去似乎也差不多是这么回事。

但一操作问题就来了。布局浪费空间,固定高度的面板让大量视口空着。工作流也很生硬。想往关卡里放东西时,界面会要求我先创建 sprite 和 entity,但 UI 并没有明确引导这个顺序。更关键的是,游戏本身是坏的:实体确实出现在屏幕上,但我完全无法控制它。继续翻代码才发现,实体定义和运行时之间的连线断了,而界面上看不出问题出在哪里。

相比之下,完整 harness 的结果一上来就更顺滑、也更完整。planner 会把一句话 prompt 扩展成一个 16 个功能点、分布在十个 sprint 中的完整规格,大幅超出单 agent 会尝试的范围。除了核心编辑器和 play mode 外,规格里还包括精灵动画系统、行为模板、音效与音乐、一个 AI 辅助 sprite 生成器与关卡设计器,以及带可分享链接的游戏导出能力。我把 frontend design skill 提供给了 planner,它会读这份 skill,并把相应的视觉设计语言一并写进规格里。每个 sprint 中,generator 和 evaluator 先协商一份契约,明确要实现哪些细节、以及之后会用哪些可测试行为验证完成。

这个版本的应用一打开就明显比单 agent 更有完成度。画布占满了视口,面板尺寸合理,界面视觉语言也和规格里的设计方向保持一致。虽然单 agent 里那些工作流笨拙的问题仍有一部分残留,比如界面仍不会明确提醒你先做 sprite 和 entity 再去铺关卡,我依然需要自己摸索,但这更像是基础模型的产品直觉不够强,而不是 harness 设计的目标没解决。它反而提示了一个以后可以继续优化的方向。

随着我继续使用这些编辑器,完整 harness 相比单 agent 的优势越来越明显。Sprite editor 更完整、工具盘更合理、颜色选择器更顺手,缩放控制也更可用。

因为我要求 planner 主动把 AI 能力织进规格,最终生成的 app 还内置了 Claude 集成,我可以通过 prompt 直接生成游戏的不同部分,这大幅加快了工作流。

最大的差别出现在 play mode:这次我真的能移动角色、能玩游戏了。虽然物理上还有一些粗糙边缘,比如角色跳到平台上后会和平台重叠,看起来很别扭,但核心机制已经能用,而单 agent 版本根本没做到这一点。当然,继续操作后我也发现 AI 自动构关卡的能力还不太行,例如前面出现一堵我跳不过去的大墙,直接把我卡死。这提示我们,未来 harness 仍然有大量“常识性改进”和“边界情况”可以继续补进去。

从日志里也能清楚看出 evaluator 如何把实现拉回规格。每个 sprint,它都会逐条走 sprint contract 中的测试标准,并通过 Playwright 驱动运行中的应用,只要行为和预期不一致就报 bug。契约本身非常细粒度,光 Sprint 3 就有 27 条标准覆盖关卡编辑器,而 evaluator 报出来的问题已经具体到可以直接修,不需要额外调查。

把 evaluator 调到这种水平并不容易。Claude 默认并不是一个好的 QA agent。最初几轮里,我经常看到它明明识别出了真实 bug,却会自己说服自己“这也没那么严重”,然后仍然让工作通过。它也容易停留在表层测试,而不去探边界情况,所以更隐蔽的问题经常漏掉。我的调优回路就是持续读 evaluator 的日志,找出它的判断与我的判断不一致的地方,再去更新 QA prompt。经过多轮迭代后,它才终于达到一个我觉得“像样”的水平。即便如此,最终输出仍然能看出当前模型 QA 能力的上限:一些小布局问题、一些交互的不自然、以及 evaluator 没深入测到的深层 bug 仍然存在。也就是说,验证环节还有很大提升空间。但和单 agent 那种“应用中心功能根本不能用”的情况相比,这个提升已经非常明显。

继续迭代 harness

第一版 harness 的结果令人鼓舞,但它同时也很重、很慢、很贵。下一步自然就是在不损失效果的前提下尽量简化它。这既是常识,也符合一个更一般性的原则:harness 中的每个组件,都隐含着“模型单独做不好什么”的假设,而这些假设总是值得反复验证,因为它们可能本来就是错的,也可能会随着模型进步迅速过时。我们在 Building Effective Agents 中把这个原则表述成 “先找尽可能简单的方案,只有在必要时才增加复杂度”,这几乎是所有 agent harness 维护者都会反复遇到的模式。

我第一次尝试简化时,直接大幅砍掉结构并引入了一些新的创意想法,但最后并没能复现原版 harness 的效果。更糟的是,我很难判断到底哪些组件是真正负重的、以及它们到底以什么方式负重。于是我改用了更方法论的路线:每次只去掉一个组件,再观察它对最终结果的影响。

在这段迭代过程中,我们也发布了 Opus 4.6,这进一步强化了“应该减掉复杂度”的理由。很明显,4.6 相比 4.5 需要的脚手架更少。官方 发布文章 里写到,它 “会更认真地规划、更擅长长时间 agent 任务、在更大的代码库里工作更可靠,并且在代码审查和调试上更能发现自己的错误”。这些恰恰都是 harness 之前在补的能力。

去掉 sprint 结构

我先尝试的是把 sprint 结构整个去掉。Sprint 原本是为了把工作拆成模型容易稳定处理的块。而随着 Opus 4.6 提升,很有理由假设模型已经可以原生承担这种工作,而不再需要如此强的拆分。

我保留了 planner 和 evaluator,因为它们都仍然明显有价值。没有 planner 时,generator 容易 scope 不足:它拿到原始 prompt 就直接开始做,最后产出的应用比 planner 扩展过的规格明显更单薄。

在移除 sprint 后,我把 evaluator 改成只在整个构建结束后跑一轮,而不再是每个 sprint 都评。随着模型能力提升,evaluator 的负重程度会因任务位置不同而变化:当任务仍处在模型单独完成的可靠边界之外时,evaluator 值得;当任务已经回到模型单独完成也很稳的区域时,evaluator 就变成了额外开销。也就是说,evaluator 并不是一个固定的 “要 / 不要” 决策,而是一个跟任务和当前模型能力边界强相关的选择。

在进行这些结构简化的同时,我还加强了 prompt,让 harness 生成的应用内建真正能驱动应用功能的 agent。这部分花了不少调试,因为相关知识足够新,Claude 训练数据里覆盖还比较薄。但经过足够多的调优之后,generator 已经能正确构建 agent。

更新版 harness 的结果

为了测试更新后的 harness,我给了它这样一个 prompt,要求生成一个 DAW(数字音频工作站):

Build a fully featured DAW in the browser using the Web Audio API.

即便简化之后,这次运行仍然很长、也很贵,大约花了 4 小时,token 成本约 124 美元。

大部分时间都花在 builder 身上,而且在没有 sprint 分解的情况下,它仍然连续稳定工作了两个多小时。

Planner 先把一句话 prompt 扩展成完整规格。从日志里我能看到,generator 先规划好 app 及 agent 设计,把 agent 接进去,再自己测试之后交给 QA。

即便如此,QA 仍然发现了真实缺口。第一轮反馈里,它指出:应用整体很强,设计不错,AI agent 也工作良好,后端也扎实,但最大缺口仍在于 Feature Completeness。应用看上去很完整,AI 集成也不错,但若干核心 DAW 功能其实还只是展示:不能拖动剪辑块、没有真正的乐器面板,也没有图形化效果器编辑器。这些不是边角问题,而是让 DAW 真正可用的核心交互,而且规格里也明确要求了它们。

第二轮反馈中,它继续抓到了几项关键缺口:音频录制还是 stub,剪辑块边缘拖拽调整和 split 没实现,效果器可视化仍然只是数字 slider,没有真正的图形界面。

也就是说,就算到了更强的模型,generator 单独工作时仍然会漏细节、会把某些功能留成 stub。QA 在抓这些“最后一公里问题”上仍然有实际价值。

从结果看,我预期它应该是一个我能在里面写旋律、和声、鼓点,把它们排成歌曲,并在过程中得到内置 agent 辅助的工具。最终产物虽然离专业音乐制作软件还差得很远,而且 agent 的编曲能力也明显还有很多改进空间,再加上 Claude 并不能真正“听见”,使得 QA 对音乐审美的反馈也更弱,但它已经具备了浏览器中一个音乐制作程序的核心要素:可用的编排视图、混音器、transport。更进一步,我甚至已经能完全通过 prompt 拼出一小段曲子:agent 会设定速度和调性、写旋律、做鼓组、调整混音、加混响。创作的核心原语已经在那里,而且 agent 自己也能通过工具把这些原语从头到尾串起来。

接下来会怎样

随着模型持续变强,我们大致可以预期它们能工作更久、处理更复杂的任务。在某些情况下,这意味着围绕模型的脚手架会随着时间推移变得没那么重要,开发者可以简单等待下一代模型,而某些问题会自然被解决。另一方面,模型越强,开发更复杂 harness 去完成更高阶任务的空间也越大。

基于这项工作,有几条经验我觉得值得继续带走。第一,围绕你实际使用的模型去做实验、读 trace、在真实任务上调优,一直都是好习惯。第二,面对更复杂的任务,把问题拆解,并为其中不同部分配置专门 agent,往往仍然有额外提升空间。第三,每当新模型发布,都应重新审视现有 harness:把已经不再负重的组件拆掉,把以前做不到的新能力补进去。

我的判断是:随着模型进步,有趣的 harness 组合并不会变少,而是会整体向前移动。AI 工程师真正有趣的工作,是不断找到下一种新的组合方式。

致谢

特别感谢 Mike Krieger、Michael Agaby、Justin Young、Jeremy Hadfield、David Hershey、Julius Tarng、Xiaoyi Zhang、Barry Zhang、Orowa Sidker、Michael Tingley、Ibrahim Madha、Martina Long 和 Canyon Robbins 对这项工作的贡献。

也感谢 Jake Eaton、Alyssa Leonard 和 Stef Sequeira 帮助打磨这篇文章。

附录

下面是 planner agent 生成的一个示例计划。